Hibernate 实现原理

ORM

ORM的全称是Object Relational Mapping,即对象关系映射。它的实现思想就是将关系数据库中表的数据映射成为对象,以对象的形式展现,这样开发人员就可以把对数据库的操作转化为对这些对象的操作。因此它的目的是为了方便开发人员以面向对象的思想来实现对数据库的操作。

Hibernate是什么

一种实现了ORM功能的框架

实现原理

实现ORM功能的时候,主要的文件有:映射类.java)、映射文件.hbm.xml)以及数据库配置文件.properties或.cfg.xml),它们各自都有各自的作用。

  • 映射类:它的作用是描述数据库表的结构,表中的字段在类中被描述成属性,将来就可以实现把表中的记录映射成为该类的对象。

  • 映射文件:它的作用是指定数据库表和映射类之间的关系,包括映射类和数据库表的对应关系、表字段和类属性类型的对应关系以及表字段和类属性名称的对应关系等。

  • 数据库配置文件:它的作用是指定与数据库连接时需要的连接信息,比如连接哪中数据库、登录用户名、登录密码以及连接字符串等。

    Hibernate如何使用反射机制的呢?在启动时读取配置文件创建Configeration实例时,根据字符串能够过得某个类的实例,并动态配饰实例属性。

工作流程如下:

创建Configeration实例

根据它的构造方法将指定的配置信息(默认hibernate.cfg.xml)读到内存。一个Configeration 实例代表Hibernate 所有Java类到Sql数据库映射的集合

创建SessionFactory实例

当使用Configeration实例创建了SessionFactory实例后,把Configeration 对象中的所有配置信息拷贝到SessionFactory的缓存中。SessionFactory的实例代表一个数据库存储源,创建后不在与Configeration 对象关联。SessionFactory是线程安全的,通常情况下,一个应用程序只有一个SessionFactory的实例。

创建Session实例

通过SessionFactory创建Session实例,session不是线程安全的,每个使用者应该用SessionFactory实例获得自己的session实例。获得session实例后就可以利用session的各种方法对对象进行持久化操作了

创建Transaction事务

Hibernate本身没有实现自己的事务管理功能,而是对底层JDBC事务或JTA事务的轻量级封装.

通过SessionbeginTransaction()方法可以得到一个对象的实例。主要用于管理实务。一个事物对象可能会包括多个对数据库进行的操作。

hibernate的缓存

为什么要使用缓存

为了降低应用程序对物理数据源访问的频次,从而提高应用程序的运行性能。缓存内的数据是对物理数据源中的数据的复制,应用程序在运行时从缓存读写数据,在特定的时刻或事件会同步缓存和物理数据源的数据。

在hibernate框架中,主要包含两个方面的缓存,一级缓存和二级缓存。hibernate缓存的作用主要表现在以下两个方面:

  1. 通过主键(ID)加载数据的时候

  2. 延迟加载中

一级缓存

hibernate的一级缓存是由session提供的,因此它只存在session的生命周期中。也就是说session关闭的时候该session所管理的一级缓存也随之被清除。hibernate的一级缓存是session所内置的,不能被卸载,也不能进行任何配置。一级缓存采用的是Key-Value的MAP方式来实现的。在缓存实体对象时,对象的主关键字ID是MAP的Key,实体对象就是对象的值。所以说一级缓存是以实体对象为单位进行存储的。访问的时候使用的是主键关键字ID。一级缓存使用的是自动维护的功能。但可以通过session提供的手动方法对一级缓存的管理进行手动干预。evict()方法用于将某个对象从session的一级缓存中清除。clear()方法用于将session缓存中的方法全部清除。

二级缓存

SessionFactory提供的缓存机制可以将缓存分为内置缓存和外置缓存。内置缓存存放了映射文件中数据的副本和预定义SQL语句。SessionFactory的外置缓存就是我们的二级缓存。它是一个可配置的插件,默认情况下SessionFactory不会启用这个插件,外置缓存的数据是数据库数据的副本。外置缓存的介质可以是内存或者硬盘。二级缓存的实现原理与一级缓存是一样的。也是通过Key-Value的Map来实现对对象的缓存。二级缓存是作用在SessionFactory范围内的。因此它比一级缓存的范围更广。它可被所有的Session对象所共享。需要注意的是放入缓存中的数据不能有第三方的应用对数据进行修改。

什么样的数据适合存放到第二级缓存?

  1. 很少被修改的数据

  2. 不是很重要的数据,允许出现偶尔并发的数据

  3. 不会被并发访问的数据

  4. 参考数据,指的是供应用参考的常量数据,它的实例数目有限,它的实例会被许多其他类的实例引用,实例极少或者从来不会被修改。

不适合存放到第二级缓存的数据?

  1. 经常被修改的数据

  2. 财务数据,绝对不允许出现并发

  3. 与其他应用共享的数据。

Hibernate实体对象的生命周期

实体对象的生命周期主要存在三种不同状态:瞬态持久态游离态

瞬态

表示该实体对象在内存中是自由自在的。与数据库中的数据没有任何关系。与session没有任何关系,也就是没有通过session的实例对其任何持久化的操作。

持久态

该实体对象处于hibernate框架所管理的状态。也就是说这个对象是与session的实体对象相关的。处于持久态的实体对象的特征就是其所作的任何的变更操作都将被Hibernate持久化到数据库中。我们可以说持久态的周期与其对应的session的周期息息相关的。hibernate会依据处于持久态的实体对象的属性变化而改变数据库中的对应记录。

游离态

处于持久态的实体对象,当不再与其对应的session对象相关联时,就处于游离态。

延迟加载

延迟加载机制是为了避免一些无谓的性能开销而提出来的,所谓延迟加载就是当在真正需要数据的时候,才真正执行数据加载操作。在Hibernate中提供了对实体对象的延迟加载以及对集合的延迟加载,另外在Hibernate3中还提供了对属性的延迟加载。

实体对象的延迟加载

如果想对实体对象使用延迟加载,必须要在实体的映射配置文件中进行相应的配置,如下所示:

1
2
3
4
5
<hibernate-mapping>
<class name=”com.neusoft.entity.User” table=”user” lazy=”true”>
……
</class>
</hibernate-mapping>

通过将class的lazy属性设置为true,来开启实体的延迟加载特性。

User user=(User)session.load(User.class,”1”);(1)
System.out.println(user.getName());(2)
当运行到(1)处时,Hibernate并没有发起对数据的查询,如果我们此时通过一些调试工具(比如JBuilder2005的Debug工具),观察此时user对象的内存快照,我们会惊奇的发现,此时返回的可能是User$EnhancerByCGLIB$$bede8986类型的对象,而且其属性为null,这是怎么回事?还记得前面我曾讲过session.load()方法,会返回实体对象的代理类对象,这里所返回的对象类型就是User对象的代理类对象。在Hibernate中通过使用CGLIB,来实现动态构造一个目标对象的代理类对象,并且在代理类对象中包含目标对象的所有属性和方法,而且所有属性均被赋值为null。通过调试器显示的内存快照,我们可以看出此时真正的User对象,是包含在代理对象的CGLIB$CALBACK_0.target属性中,当代码运行到(2)处时,此时调用user.getName()方法,这时通过CGLIB赋予的回调机制,实际上调用CGLIB$CALBACK_0.getName()方法,当调用该方法时,Hibernate会首先检查CGLIB$CALBACK_0.target属性是否为null,如果不为空,则调用目标对象的getName方法,如果为空,则会发起数据库查询,生成类似这样的SQL语句:select * from user where id=’1’;来查询数据,并构造目标对象,并且将它赋值到CGLIB$CALBACK_0.target属性中。

这样,通过一个中间代理对象,Hibernate实现了实体的延迟加载,只有当用户真正发起获得实体对象属性的动作时,才真正会发起数据库查询操作。所以实体的延迟加载是用通过中间代理类完成的,所以只有session.load()方法才会利用实体延迟加载,因为只有session.load()方法才会返回实体类的代理类对象。

集合类型的延迟加载

属性延迟加载

在Hibernate3中,引入了一种新的特性——属性的延迟加载,这个机制又为获取高性能查询提供了有力的工具。在前面我们讲大数据对象读取时,在User对象中有一个resume字段,该字段是一个java.sql.Clob类型,包含了用户的简历信息,当我们加载该对象时,我们不得不每一次都要加载这个字段,而不论我们是否真的需要它,而且这种大数据对象的读取本身会带来很大的性能开销。在Hibernate2中,我们只有通过我们前面讲过的面性能的粒度细分,来分解User类,来解决这个问题(请参照那一节的论述),但是在Hibernate3中,我们可以通过属性延迟加载机制,来使我们获得只有当我们真正需要操作这个字段时,才去读取这个字段数据的能力,为此我们必须如下配置我们的实体类:

1
2
3
4
5
6
<hibernate-mapping>
<class name=”com.neusoft.entity.User” table=”user”>
……
<property name=”resume” type=”java.sql.Clob” column=”resume” lazy=”true”/>
</class>
</hibernate-mapping>

通过对元素的lazy属性设置true来开启属性的延迟加载

Hibernate的优势

  • 对象化,以面向对象的思想操作数据库里的数据。

  • 移植性,不用考虑数据库之间的差异性,更换数据库只需要修改配置即可

  • 开发效率,数据的操作和对象关联,开发者不需要写大量的SQL语句

  • 提供了缓存机制,session缓存,二级缓存和查询缓存。

Hibernate的劣势

  • 过度封装JDBC,导致了失去了对SQL的控制,不够灵活,难以优化。

  • 没有专门的批处理机制

  • 内存消耗比较大,要做数据和对象的转换

  • 复杂查询不给力,不如mybatis

http://www.cnblogs.com/kubixuesheng/p/7497963.html#3780947